﻿namespace MicroCloud.Utils
{
    /// <summary>
    /// 中国历法
    /// </summary>
    public class ChineseCalendar
    {
        //字段
        private readonly ChineseLunisolarCalendar _chineseLunisolarCalendar = new();

        private readonly string[] _chineseNumber = { "〇", "一", "二", "三", "四", "五", "六", "七", "八", "九" };
        private readonly string[] _chineseMonth =
        {
            "正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊"
        };
        private static readonly string[] _chineseDay =
        {
            "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十",
            "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十",
            "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"
        };
        private static readonly string[] _chineseWeek =
        {
            "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"
        };

        private static readonly string[] _celestialStem = { "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" };
        private static readonly string[] _terrestrialBranch = { "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" };
        private static readonly string[] _chineseZodiac = { "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪" };

        private readonly string[] _solarTerm =
        {
            "小寒", "大寒", "立春", "雨水", "惊蛰", "春分",
            "清明", "谷雨", "立夏", "小满", "芒种", "夏至",
            "小暑", "大暑", "立秋", "处暑", "白露", "秋分",
            "寒露", "霜降", "立冬", "小雪", "大雪", "冬至"
        };
        private readonly int[] _solarTermInfo = {
            0, 21208, 42467, 63836, 85337, 107014, 128867, 150921, 173149, 195551, 218072, 240693, 263343, 285989,
            308563, 331033, 353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758
        };
        
        /// <summary>
        /// 初始化一个中国历法的新实例
        /// </summary>
        /// <param name="dateTime">日期时间</param>
        /// <param name="gregorianCalendarDate">是否公历日期，否则为农历日期</param>
        public ChineseCalendar(DateTime dateTime, bool gregorianCalendarDate = true)
        {
            Check.NotNull(dateTime, nameof(dateTime));

            if (gregorianCalendarDate)
            {
                GregorianCalendarDate = dateTime;
                ChineseCalendarDate = ToChineseCalendarDate(GregorianCalendarDate);
            }
            else
            {
                ChineseCalendarDate = dateTime;
                GregorianCalendarDate = ToGregorianCalendarDate(ChineseCalendarDate);
            }
            IsLeapYear = _chineseLunisolarCalendar.IsLeapYear(ChineseCalendarDate.Year);
            IsLeapMonth = _chineseLunisolarCalendar.IsLeapMonth(ChineseCalendarDate.Year, ChineseCalendarDate.Month);
            IsLeapDay = _chineseLunisolarCalendar.IsLeapDay(ChineseCalendarDate.Year, ChineseCalendarDate.Month, ChineseCalendarDate.Day);

            SexagenaryYear = GetSexagenaryYear(ChineseCalendarDate);
            HeavenlyStem = GetHeavenlyStem(SexagenaryYear);
            EarthlyBranch = GetEarthlyBranch(SexagenaryYear);

            SolarTerm = GetSolarTerm();
            ZodiacSign = GetZodiacSign();
            StarSign = GetStarSign();
        }

        #region 属性
        /// <summary>
        /// 获取 当前日历
        /// </summary>
        public static ChineseCalendar Now => new(DateTime.Now);

        /// <summary>
        /// 获取 公历日期
        /// </summary>
        public DateTime GregorianCalendarDate { get; }
        
        /// <summary>
        /// 获取 农历日期
        /// </summary>
        public DateTime ChineseCalendarDate { get; }

        /// <summary>
        /// 获取 是否为闰年
        /// </summary>
        public bool IsLeapYear { get; }

        /// <summary>
        /// 获取 是否为闰月
        /// </summary>
        public bool IsLeapMonth { get; }

        /// <summary>
        /// 获取 是否为闰日
        /// </summary>
        public bool IsLeapDay { get; }

        /// <summary>
        /// 获取 甲子（60 年）循环中的年份
        /// </summary>
        public int SexagenaryYear { get; }

        /// <summary>
        /// 获取 纪元年天干
        /// </summary>
        public HeavenlyStem HeavenlyStem { get; }

        /// <summary>
        /// 获取 纪元年地支
        /// </summary>
        public EarthlyBranch EarthlyBranch { get; }

        /// <summary>
        /// 获取 节气
        /// </summary>
        public SolarTerm SolarTerm { get; }

        /// <summary>
        /// 获取 生肖
        /// </summary>
        public ChineseZodiacSign ZodiacSign { get; }

        /// <summary>
        /// 获取 星座
        /// </summary>
        public StarSign StarSign { get; }

        #endregion

        #region 方法
        /// <summary>
        /// 获取农历日期纪元年
        /// </summary>
        /// <returns></returns>
        public string GetChineseEraYear()
        {
            return $"{HeavenlyStem.ToDescription()}{EarthlyBranch.ToDescription()}";
        }

        /// <summary>
        /// 获取农历日期纪元月
        /// </summary>
        /// <returns></returns>
        public string GetChineseEraMonth()
        {
            #region ====== 节令法 ======
            var solarIndex = SolarTermFunc((x, y) => x <= y, out var dt);
            solarIndex = solarIndex == -1 ? 23 : solarIndex;
            var currentIndex = (int)Math.Floor(solarIndex / (decimal)2);

            //天干         
            var solarMonth = currentIndex == 0 ? 11 : currentIndex - 1; //计算天干序(月份)
            var sexagenaryYear = _chineseLunisolarCalendar.GetSexagenaryYear(ChineseCalendarDate);
            var stemYear = _chineseLunisolarCalendar.GetCelestialStem(sexagenaryYear) - 1;
            if (solarMonth == 0) //立春时，春节前后的不同处理
            {
                var year = _chineseLunisolarCalendar.GetYear(dt);
                var month = _chineseLunisolarCalendar.GetMonth(dt);
                stemYear = year == ChineseCalendarDate.Year && month != 1 ? stemYear + 1 : stemYear;
            }
            if (solarMonth == 11) //立春在春节后，对前一节气春节前后不同处理
            {
                var year = _chineseLunisolarCalendar.GetYear(dt);
                stemYear = year != ChineseCalendarDate.Year ? stemYear - 1 : stemYear;
            }
            int stemIndex;
            switch (stemYear)
            {
                case 0:
                case 5:
                    stemIndex = 3;
                    break;
                case 1:
                case 6:
                    stemIndex = 5;
                    break;
                case 2:
                case 7:
                    stemIndex = 7;
                    break;
                case 3:
                case 8:
                    stemIndex = 9;
                    break;
                default:
                    stemIndex = 1;
                    break;
            }
            //天干序
            stemIndex = (stemIndex - 1 + solarMonth) % 10;

            //地支序
            var branchIndex = currentIndex >= 11 ? currentIndex - 11 : currentIndex + 1;

            return $"{_celestialStem[stemIndex]}{_terrestrialBranch[branchIndex]}";

            #endregion
        }

        /// <summary>
        /// 获取农历日期纪元日
        /// </summary>
        /// <returns></returns>
        public string GetChineseEraDay()
        {
            var ts = ChineseCalendarDate - new DateTime(1901, 2, 15);
            var offset = ts.Days;
            var sexagenaryDay = offset % 60;
            return $"{_celestialStem[sexagenaryDay % 10]}{_terrestrialBranch[sexagenaryDay % 12]}";
        }

        /// <summary>
        /// 获取农历日期纪元时
        /// </summary>
        /// <returns></returns>
        public string GetChineseEraHour()
        {
            var hourIndex = (int)Math.Floor((ChineseCalendarDate.Hour + 1) / (decimal)2);
            hourIndex = hourIndex == 12 ? 0 : hourIndex;
            return _terrestrialBranch[hourIndex];
        }

        /// <summary>
        /// 获取农历日期纪元分
        /// </summary>
        /// <returns></returns>
        public string GetChineseEraMinute()
        {
            var realMinute = (ChineseCalendarDate.Hour % 2 == 0 ? 60 : 0) + ChineseCalendarDate.Minute;
            return $"{_chineseNumber[(int)Math.Floor(realMinute / (decimal)30) + 1]}";
        }

        /// <summary>
        /// 获取农历日期年
        /// </summary>
        /// <returns></returns>
        public string GetChineseYear()
        {
            var yearArray = Array.ConvertAll(ChineseCalendarDate.Year.ToString().ToCharArray(), x => int.Parse(x.ToString()));
            var year = new StringBuilder();
            foreach (var item in yearArray)
            {
                year.Append(_chineseNumber[item]);
            }
            return $"{GetLeapString(IsLeapYear)}{year}";
        }

        /// <summary>
        /// 获取农历日期月
        /// </summary>
        /// <returns></returns>
        public string GetChineseMonth()
        {
            return $"{GetLeapString(IsLeapMonth)}{_chineseMonth[ChineseCalendarDate.Month - 1]}";
        }

        /// <summary>
        /// 获取农历日期日
        /// </summary>
        /// <returns></returns>
        public string GetChineseDay()
        {
            return _chineseDay[ChineseCalendarDate.Day - 1];
        }

        /// <summary>
        /// 获取农历日期时
        /// </summary>
        /// <returns></returns>
        public string GetChineseHour()
        {
            var hourArray = Array.ConvertAll(ChineseCalendarDate.Hour.ToString().ToCharArray(), x => int.Parse(x.ToString()));
            var hour = new StringBuilder();
            foreach (var item in hourArray)
            {
                hour.Append(_chineseNumber[item]);
            }
            return hour.ToString();
        }

        /// <summary>
        /// 获取农历日期分
        /// </summary>
        /// <returns></returns>
        public string GetChineseMinute()
        {
            var minuteArray = Array.ConvertAll(ChineseCalendarDate.Minute.ToString().ToCharArray(), x => int.Parse(x.ToString()));
            var minute = new StringBuilder();
            foreach (var item in minuteArray)
            {
                minute.Append(_chineseNumber[item]);
            }
            return minute.ToString();
        }

        /// <summary>
        /// 获取农历日期秒
        /// </summary>
        /// <returns></returns>
        public string GetChineseSecond()
        {
            var secondArray = Array.ConvertAll(ChineseCalendarDate.Second.ToString().ToCharArray(), x => int.Parse(x.ToString()));
            var second = new StringBuilder();
            foreach (var item in secondArray)
            {
                second.Append(_chineseNumber[item]);
            }
            return second.ToString();
        }

        /// <summary>
        /// 获取星期字符
        /// </summary>
        /// <returns></returns>
        public string GetWeekString()
        {
            return _chineseWeek[(int)GregorianCalendarDate.DayOfWeek]; ;
        }

        /// <summary>
        /// 获取天干字符
        /// </summary>
        /// <returns></returns>
        public string GetHeavenlyStemString()
        {
            return HeavenlyStem.ToDescription();
        }

        /// <summary>
        /// 获取地支字符
        /// </summary>
        /// <returns></returns>
        public string GetEarthlyBranchString()
        {
            return EarthlyBranch.ToDescription();
        }

        /// <summary>
        /// 获取节气字符
        /// </summary>
        /// <returns></returns>
        public string GetSolarTermString()
        {
            if (SolarTerm == SolarTerm.None)
            {
                return "";
            }
            return SolarTerm.ToDescription();
        }

        /// <summary>
        /// 获取生肖字符
        /// </summary>
        /// <returns></returns>
        public string GetZodiacSignsString()
        {
            return ZodiacSign.ToDescription();
        }

        /// <summary>
        /// 获取星座字符
        /// </summary>
        /// <returns></returns>
        public string GetStarSignString()
        {
            return StarSign.ToDescription();
        }

        #endregion

        #region 私有方法
        /// <summary>
        /// 公历日期转农历日期
        /// </summary>
        /// <param name="dateTime">公历日期时间</param>
        /// <returns>农历日期</returns>
        private DateTime ToChineseCalendarDate(DateTime dateTime)
        {
            if (dateTime > _chineseLunisolarCalendar.MaxSupportedDateTime || dateTime < _chineseLunisolarCalendar.MinSupportedDateTime)
            {
                throw new Exception(
                    I18N.T("日期时间必须在 {0} 到 {1} 范围内", _chineseLunisolarCalendar.MinSupportedDateTime.ToShortDateString(), _chineseLunisolarCalendar.MaxSupportedDateTime.ToShortDateString())
                    );
            }

            int year = _chineseLunisolarCalendar.GetYear(dateTime);
            int month = _chineseLunisolarCalendar.GetMonth(dateTime);
            int dayOfMonth = _chineseLunisolarCalendar.GetDayOfMonth(dateTime);
            int leapMonth = _chineseLunisolarCalendar.GetLeapMonth(year);
            bool isLeapMonth = _chineseLunisolarCalendar.IsLeapMonth(year, month);
            bool isLeapYear = _chineseLunisolarCalendar.IsLeapYear(year);

            if (isLeapMonth || isLeapYear && month >= leapMonth)
            {
                month -= 1;
            }

            return new DateTime(year, month, dayOfMonth, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);
        }
        
        /// <summary>
        /// 农历日期转公历日期
        /// </summary>
        /// <param name="dateTime">农历日期时间</param>
        /// <returns>公历日期</returns>
        private DateTime ToGregorianCalendarDate(DateTime dateTime)
        {
            int LeapMonth = _chineseLunisolarCalendar.GetLeapMonth(dateTime.Year, 1);
            bool isLeapMonth = _chineseLunisolarCalendar.IsLeapMonth(dateTime.Year, dateTime.Month);

            if (isLeapMonth)
            {
                dateTime = _chineseLunisolarCalendar.ToDateTime(dateTime.Year, dateTime.Month - 1, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);
            }
            else if (dateTime.Month > LeapMonth)
            {
                dateTime = _chineseLunisolarCalendar.ToDateTime(dateTime.Year, dateTime.Month + 1, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);
            }
            else
            {
                dateTime = _chineseLunisolarCalendar.ToDateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);
            }

            return dateTime;
        }
        
        /// <summary>
        /// 获取甲子（60 年）循环中的年份
        /// </summary>
        /// <param name="date">农历日期</param>
        /// <returns>甲子循环中的一个从 1 到 60 的数字</returns>
        private int GetSexagenaryYear(DateTime date)
        {
            return _chineseLunisolarCalendar.GetSexagenaryYear(date);
        }

        /// <summary>
        /// 获取天干枚举
        /// </summary>
        /// <param name="sexagenaryYear">一个从 1 到 60 的整数，用于表示甲子循环中的一年</param>
        /// <returns>天干枚举</returns>
        private HeavenlyStem GetHeavenlyStem(int sexagenaryYear)
        {
            if (sexagenaryYear > 60 || sexagenaryYear < 1)
            {
                throw new Exception(I18N.T("甲子年份 {0} 必须在 {1} 到 {2} 范围内", sexagenaryYear, 1, 60));
            }

            var celestialStem = _chineseLunisolarCalendar.GetCelestialStem(sexagenaryYear);
            return celestialStem switch
            {
                1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9 or 10 => (HeavenlyStem)celestialStem,
                _ =>
                throw new Exception(I18N.T("天干数值 {0} 必须在 {1} 到 {2} 范围内", celestialStem, 1, 10))
                ,
            };
        }

        /// <summary>
        /// 获取地支枚举
        /// </summary>
        /// <param name="sexagenaryYear">一个从 1 到 60 的整数，用于表示甲子循环中的一年</param>
        /// <returns>地支枚举</returns>
        private EarthlyBranch GetEarthlyBranch(int sexagenaryYear)
        {
            if (sexagenaryYear > 60 || sexagenaryYear < 1)
            {
                //throw new MicroCloudException($"甲子年份 {sexagenaryYear} 必须在 {1} 到 {60} 范围内");
                throw new Exception(I18N.T("甲子年份 {0} 必须在 {1} 到 {2} 范围内", sexagenaryYear, 1, 60));
            }

            var terrestrialBranch = _chineseLunisolarCalendar.GetTerrestrialBranch(sexagenaryYear);
            return terrestrialBranch switch
            {
                1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9 or 10 or 11 or 12 => (EarthlyBranch)terrestrialBranch,
                _ =>
                 throw new Exception(I18N.T("地支数值 {0} 必须在 {1} 到 {2} 范围内", terrestrialBranch, 1, 12))
                ,
            };
        }

        /// <summary>
        /// 获取节气枚举
        /// </summary>
        /// <returns>地支枚举</returns>
        private SolarTerm GetSolarTerm()
        {
            var i = SolarTermFunc((x, y) => x == y, out var dt);
            if (i == -1)
            {
                return SolarTerm.None;
            }
            var solarTermString = _solarTerm[i];
            return solarTermString switch
            {
                "立春" => SolarTerm.BeginningOfSpring,
                "雨水" => SolarTerm.Rains,
                "惊蛰" => SolarTerm.AwakeningOfInsects,
                "春分" => SolarTerm.VernalEquinox,
                "清明" => SolarTerm.TombSweeping,
                "谷雨" => SolarTerm.GrainRain,
                "立夏" => SolarTerm.BeginningOfSummer,
                "小满" => SolarTerm.LesserFullnessOfGrain,
                "芒种" => SolarTerm.GrainInEar,
                "夏至" => SolarTerm.SummerSolstice,
                "小暑" => SolarTerm.SlightHeat,
                "大暑" => SolarTerm.GreatHeat,
                "立秋" => SolarTerm.BeginningOfAutumn,
                "处暑" => SolarTerm.LimitOfHeat,
                "白露" => SolarTerm.WhiteDew,
                "秋分" => SolarTerm.AutumnalEquinox,
                "寒露" => SolarTerm.ColdDew,
                "霜降" => SolarTerm.FirstFrost,
                "立冬" => SolarTerm.BeginningOfWinter,
                "小雪" => SolarTerm.LightSnow,
                "大雪" => SolarTerm.GreaterSnow,
                "冬至" => SolarTerm.WinterSolstice,
                "小寒" => SolarTerm.LesserCold,
                "大寒" => SolarTerm.GreatCold,
                _ => SolarTerm.None,
            };
        }

        /// <summary>
        /// 获取生肖枚举
        /// </summary>
        /// <returns>生肖枚举</returns>
        private ChineseZodiacSign GetZodiacSign()
        {
            var zodiacSignString = _chineseZodiac[(ChineseCalendarDate.Year - 4) % 12];
            return zodiacSignString switch
            {
                "鼠" => ChineseZodiacSign.Rat,
                "牛" => ChineseZodiacSign.OX,
                "虎" => ChineseZodiacSign.Tiger,
                "兔" => ChineseZodiacSign.Rabbit,
                "龙" => ChineseZodiacSign.Dragon,
                "蛇" => ChineseZodiacSign.Snake,
                "马" => ChineseZodiacSign.Horse,
                "羊" => ChineseZodiacSign.Sheep,
                "猴" => ChineseZodiacSign.Monkey,
                "鸡" => ChineseZodiacSign.Rooster,
                "狗" => ChineseZodiacSign.Dog,
                "猪" => ChineseZodiacSign.Pig,
                _ =>
                throw new Exception(
                    I18N.T("生肖 {0} 值无效", zodiacSignString))
                ,
            };
        }

        /// <summary>
        /// 获取星座枚举
        /// </summary>
        /// <returns>星座枚举</returns>
        private StarSign GetStarSign()
        {
            var dayF = GregorianCalendarDate.Month == 1 && GregorianCalendarDate.Day < 20 ? 13 + GregorianCalendarDate.Day / 100f : GregorianCalendarDate.Month + GregorianCalendarDate.Day / 100f;

            if (GregorianCalendarDate.Month == 1 && GregorianCalendarDate.Day < 20)
            {
                if (GregorianCalendarDate.Day < 10) { dayF = float.Parse(string.Format("13.0{0}", GregorianCalendarDate.Day)); }
                else { dayF = float.Parse(string.Format("13.{0}", GregorianCalendarDate.Day)); }
            }
            else
            {
                if (GregorianCalendarDate.Day < 10) { dayF = float.Parse(string.Format("{0}.0{1}", GregorianCalendarDate.Month, GregorianCalendarDate.Day)); }
                else { dayF = float.Parse(string.Format("{0}.{1}", GregorianCalendarDate.Month, GregorianCalendarDate.Day)); }
            }

            string[] atoms = { "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "魔羯座" };
            float[] atomBound = { 1.20F, 2.20F, 3.21F, 4.21F, 5.21F, 6.22F, 7.23F, 8.23F, 9.23F, 10.23F, 11.21F, 12.22F, 13.20F };

            StarSign starSign = StarSign.Capricorn;
            var enumEntities = starSign.ToList();

            string ret = "";
            for (int i = 0; i < atomBound.Length - 1; i++)
            {
                if (atomBound[i] <= dayF && atomBound[i + 1] > dayF)
                {
                    ret = atoms[i];
                    break;
                }
            }
            return (StarSign)enumEntities.FirstOrDefault(o => o.Desc == ret).Value;
        }

        /// <summary>
        /// 获取润年/月/日字符串
        /// </summary>
        /// <param name="isLeep"></param>
        /// <param name="isChinese"></param>
        /// <returns></returns>
        private string GetLeapString(bool isLeep, bool isChinese = true)
        {
            return isLeep ? isChinese ? "润" : "L" : "";
        }

        /// <summary>
        /// 节气计算（当前年），返回指定条件的节气序及日期（公历）
        /// </summary>
        /// <param name="func"></param>
        /// <param name="dateTime"></param>
        /// <returns>-1时即没找到</returns>
        private int SolarTermFunc(Expression<Func<int, int, bool>> func, out DateTime dateTime)
        {
            var baseDateAndTime = new DateTime(1900, 1, 6, 2, 5, 0); //#1/6/1900 2:05:00 AM#
            var year = ChineseCalendarDate.Year;
            int[] solar = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
            var expressionType = func.Body.NodeType;
            if (expressionType != ExpressionType.LessThan && expressionType != ExpressionType.LessThanOrEqual &&
                expressionType != ExpressionType.GreaterThan && expressionType != ExpressionType.GreaterThanOrEqual &&
                expressionType != ExpressionType.Equal)
            {
                throw new NotSupportedException(I18N.T("不受支持的操作符"));
            }

            if (expressionType == ExpressionType.LessThan || expressionType == ExpressionType.LessThanOrEqual)
            {
                solar = solar.OrderByDescending(x => x).ToArray();
            }
            foreach (var item in solar)
            {
                var num = 525948.76 * (year - 1900) + _solarTermInfo[item - 1];
                var newDate = baseDateAndTime.AddMinutes(num); //按分钟计算
                if (func.Compile()(newDate.DayOfYear, ChineseCalendarDate.DayOfYear))
                {
                    dateTime = newDate;
                    return item - 1;
                }
            }
            dateTime = _chineseLunisolarCalendar.MinSupportedDateTime;
            return -1;
        }

        #endregion
    }
}